package libra;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphO;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;

/**
 * Sentinel-Core核心包测试用例
 *	FlowRule：流控规则
 *	DegradeRule：降级规则
 */
@RestController
@RequestMapping("test")
public class TestController {
	static final String 
		// 定义资源名，资源名可使用任意有业务语义的字符串，比如方法名、接口名或其它可唯一标识的字符串。
		Resource_Name_Flow_Code = "/test/flowRuleCode", // 基于代码的方式集成“服务流控”
		Resource_Name_Flow_Anno="/test/flowRuleAnno", // 基于注解的方式集成“服务流控”
		Resource_Name_Degrade_Code = "/test/degradeRuleCode", // 基于代码的方式，集成“服务降级”
		Resource_Name_Degrade_Anno = "/test/degradeRuleAnno" // 基于注解的方式，集成“服务降级”
	;
	// 构造响应返回
	private Map<String, String> createResp( String state ) {
		return new HashMap<String, String>() {{
			put("state", state);
		}};
	}
	
	/****************************** 服务流控测试 ***********************************/
	/**
	 * Sentinel流控
	 * 以“抛出异常的方式”定义资源
	 * https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8
	 */
	@RequestMapping("flowRuleCode")
	public Map flowRuleCode() {
		/*
		 * Sentinel通过SphU.entry()针对资源进行限制，通过entry.exit()对针对资源进行释放。
		 * Entry实现了AutoCloseable接口，因此可以省略finally代码块，自动调用完成后关闭。
		 */
		try ( Entry entry = SphU.entry(Resource_Name_Flow_Code) ) {
			/* 您的业务逻辑 - 开始 */
			System.out.println("/test/flowRuleCode...正常执行..." + LocalTime.now());
			/* 您的业务逻辑 - 结束 */
			return createResp("flowRuleCode.success");
		} catch (BlockException e) {
			// 捕获到com.alibaba.csp.sentinel.slots.block.BlockException异常，则表示资源访问被阻止（限流 或 降级）
			/* 流控逻辑处理 - 开始 */
			System.out.println("/test/flowRuleCode...block..." + LocalTime.now());
			/* 流控逻辑处理 - 结束 */
//			e.printStackTrace();
			return createResp("flowRuleCode.block【流控限流】");
		} catch (Exception ex) {
			// 非com.alibaba.csp.sentinel.slots.block.BlockException异常，则为程序本身执行异常。
			System.out.println("ex!");
			return createResp("flowRuleCode.exception");
		} 
		// 如果没有采用try(){..}catch(ex){..}的新语法，则必须要在逻辑代码执行完后，手动调用exit()退出调用链，清理当前线程的上下文。
//		finally {
//			if (entry != null) 
//				entry.exit();
//		}
//		return "fail";
	}
	
	/**
	 * Sentinel流控
	 * 以“注解方式”@SentinelResource定义资源
	 * 使用Sentinel注解，必须导入sentinel-annotation-aspectj依赖，
	 * 同时需要在容器中配置SentinelResourceAspect的bean对象
	 * 		value：必需项，资源名称，且必须全局唯一
	 * 		blockHandler / blockHandlerClass：二选一配置
	 * 		  blockHandler：可选项，应处理 BlockException 的函数名称。
	 * 						该函数的访问权限必须是public，且该函数必须与目标方法处在同一个类中。
	 * 						参数类型需要和原方法相匹配并且最后加一个额外的参数，类型为 BlockException。
	 * 						返回类型需要与原方法相匹配。
	 *		  blockHandlerClass：可选项，可选项，应处理 BlockException 的函数所在类的.class字节码。
	 *						该类的处理函数的访问权限必须是public static。
	 *						参数类型需要和原方法相匹配并且最后加一个额外的参数，类型为 BlockException。
	 * 						返回类型需要与原方法相匹配。
	 * 
	 * 		fallback / fallbackClass：
	 * 		 fallback：可选项，用于在目标函数抛出异常的时候提供 fallback 处理逻辑的函数名称。
	 * 					该函数的访问权限必须是public，且该函数必须与目标方法处在同一个类中。
	 * 					可以针对所有类型的异常（除了 exceptionsToIgnore 里面排除掉的异常类型）进行处理。
	 * 【blockHandler 和 fallback 同时指定时，blockHandler的优先级更高，Sentinel会优先选择 blockHandler 进行处理】
	 * 		exceptionsToIgnore：可选项，指定需要忽略，不做fallback处理的异常类型。
	 * 
	 * 
	 * 
	 * https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81
	 */
	@RequestMapping("flowRuleAnno")
	@SentinelResource( 
		value = Resource_Name_Flow_Anno, 
		blockHandler = "FlowAnnotationBlockHanbler",
		fallback = "FlowAnnotationFallback",
		exceptionsToIgnore = {ArithmeticException.class}
	)
	public Map flowRuleAnno( Integer type ) throws Exception {
		if( type == 1 ) {
			int i = 1/0;
		} else if ( type == 2 ) {
			throw new IllegalArgumentException("测试异常_捕获");
		}
		System.out.println("/test/flowRuleAnno...正常执行..." + LocalTime.now());
		return createResp("flowRuleAnno.success");
	}
	public Map FlowAnnotationBlockHanbler( Integer type, BlockException ex ) throws Exception {
//		ex.printStackTrace();
		System.out.println("/test/flowRuleAnno...block..." + LocalTime.now() + "...【"+ ex.getMessage() +"】");
		return createResp("flowRuleAnno.block【流控限流】");
	}
	public Map FlowAnnotationFallback( Integer type, Throwable ex ) throws Exception {
//		ex.printStackTrace();
		System.out.println("/test/flowRuleAnno...error【"+ ex.getMessage() +"】");
		return createResp("flowRuleAnno.error");
	}
	
	/**
	 * 定义FlowRule流控规则
	 * 当前案例设置为每秒只能访问1次
	 */
	@PostConstruct
	private static void initFlowRules(){
		// 定义list集合，用于存储多个规则
	    List<FlowRule> rules = new ArrayList<>();
	    
	    /* 为 通过代码方式定义的资源 配置规则 */
	    // 创建流控规则
	    FlowRule rule_code = new FlowRule();
	    // 设置受保护资源，将当前“规则”与“资源”进行绑定 
	    rule_code.setResource(Resource_Name_Flow_Code);
	    // 设置流控规则QPS
	    rule_code.setGrade(RuleConstant.FLOW_GRADE_QPS);
	    // 设置受保护的资源阈值，当前案例设置为每秒只能访问1次
	    rule_code.setCount(1);
	    // 将当前规则添加进list集合
	    rules.add(rule_code);
	    
	    
	    /* 为 通过@SentinelResource注解方式定义的资源 配置规则 */
	    // 创建流控规则
	    FlowRule rule_anno = new FlowRule();
	    // 设置受保护资源，将当前“规则”与“资源”进行绑定 
	    rule_anno.setResource(Resource_Name_Flow_Anno);
	    // 设置流控规则QPS
	    rule_anno.setGrade(RuleConstant.FLOW_GRADE_QPS);
	    // 设置受保护的资源阈值，当前案例设置为每秒只能访问1次
	    rule_anno.setCount(1);
	    // 将当前规则添加进list集合
	    rules.add(rule_anno);
	    
	    // 流控规则管理器FlowRuleManager加载所有配置好的规则
	    FlowRuleManager.loadRules(rules);
	}
	
	
	/****************************** 服务降级测试 ***********************************/
	/**
	 * 定义DegradeRule降级规则
	 */
	@PostConstruct
	private static void initDegradeRules(){
		// 定义list集合，用于存储多个规则
		List<DegradeRule> degradeRules = new ArrayList<>();
		
		/* 为 通过@SentinelResource注解方式定义的资源 配置规则 */
		// 创建降级规则
		DegradeRule degradeRule_code = new DegradeRule();
		// 设置受保护资源，将当前“规则”与“资源”进行绑定 
		degradeRule_code.setResource(Resource_Name_Degrade_Code);
		// 设置规则策略，当前案例设置为异常数统计
		degradeRule_code.setGrade( RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT );
		// 设置异常数阈值，即请求累计有2次异常，就进行熔断
		degradeRule_code.setCount(2);
		// 触发熔断的最小请求数，即在最小请求数中达到了异常阈值开始出发熔断机制
		degradeRule_code.setMinRequestAmount(2);
		// 统计时长（1.8开始引入），默认1000，单位ms
		degradeRule_code.setStatIntervalMs( 30*1000 );
		// 熔断的持续时长（时间窗口），单位s，超过时间窗口后，会恢复当前接口，但是恢复后的第一次就出现异常，会再次直接进入熔断状态，而不会再次进入判定状态。
		degradeRule_code.setTimeWindow(10);
		// 将当前规则添加进list集合
		degradeRules.add(degradeRule_code);
		
		/* 为 通过@SentinelResource注解方式定义的资源 配置规则 */
		// 创建降级规则
		DegradeRule degradeRule_anno = new DegradeRule();
		// 设置受保护资源，将当前“规则”与“资源”进行绑定 
		degradeRule_anno.setResource(Resource_Name_Degrade_Anno);
		// 设置规则策略，当前案例设置为异常数统计
		/*
		 * 熔断策略：
		 * 	异常数策略：RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT
		 * 	慢调用比例：RuleConstant.DEGRADE_GRADE_RT???（1.8及以上）
		 * 	异常比例：RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO
		 */
		degradeRule_anno.setGrade( RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT );
		// 设置异常数阈值，即请求累计有2次异常，就进行熔断
		degradeRule_anno.setCount(2);
		// 触发熔断的最小请求数，即在最小请求数中达到了异常阈值开始出发熔断机制
		degradeRule_anno.setMinRequestAmount(2);
		// 统计时长（1.8开始引入），默认1000，单位ms
		degradeRule_anno.setStatIntervalMs( 30*1000 );
		// 熔断的持续时长（时间窗口），单位s，超过时间窗口后，会恢复当前接口，但是恢复后的第一次就出现异常，会再次直接进入熔断状态，而不会再次进入判定状态。
		degradeRule_anno.setTimeWindow(10);
		// 将当前规则添加进list集合
		degradeRules.add(degradeRule_anno);
		/* 总结，
		 * 在“统计时长”内，首先满足“最小请求数”的请求量，然后在这些请求中出现异常的数量达到“异常阈值”。
		 * 就会持续熔断“持续时长”的时段，超过“熔断时长”后，会回复接口调用。
		 * 但是在恢复接口调用的第一次就出现异常的话，
		 * 程序会直接再次进入熔断状态，而不会再次根据“统计时长”、“最小请求数”、“异常阈值”这些条件进行熔断标准的判定。
		 * 依次往复...
		 */
		
		// 降级规则管理器DegradeRuleManager加载所有配置好的规则
		DegradeRuleManager.loadRules(degradeRules);
	}
	
	/**
	 * Sentinel流控????
	 * 以“抛出异常的方式”定义资源
	 * https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8
	 */
	@RequestMapping("degradeRuleCode")
	public Map degradeRuleCode( Integer type ) throws Exception {
//		// 资源名可使用任意有业务语义的字符串
//		if (SphO.entry(Resource_Name_Degrade_Code, EntryType.IN)) {
//			// 务必保证finally会被执行
//			try {
//				/**
//				 * 被保护的业务逻辑
//				 */
//				if( type == 1 ) {
//					int i = 1/0;
//				} else if ( type == 2 ) {
//					throw new IllegalArgumentException("测试异常_捕获");
//				}
//				System.out.println("/test/degradeRuleAnno...正常执行..." + LocalTime.now() );
//				return createResp("degradeRuleCode.success");
//			} finally {
//				SphO.exit();
//			}
//		} else {
//			// 资源访问阻止，被限流或被降级
//			// 进行相应的处理操作
//			
//		}
		try (Entry entry = SphU.entry(Resource_Name_Degrade_Code, EntryType.IN) ) {
			System.out.println( "/test/degradeRuleCode...in..." );
			/**
			 * 被保护的业务逻辑
			 */
			if( type == 1 ) {
				int i = 1/0;
			} else if ( type == 2 ) {
				throw new IllegalArgumentException("测试异常_捕获");
			}
			System.out.println("/test/degradeRuleAnno...正常执行..." + LocalTime.now() );
			return createResp("degradeRuleCode.success");
		} catch (Throwable t) {
			if (!BlockException.isBlockException(t)) {
				Tracer.trace(t);
			}
			System.out.println("/test/degradeRuleCode...block..." + LocalTime.now());
			return createResp("degradeRuleCode.block【熔断降级】");
		}
	}
	
	@RequestMapping("degradeRuleAnno")
	@SentinelResource(
		value = Resource_Name_Degrade_Anno,
		entryType = EntryType.IN,
		blockHandler = "degradeRuleAnnoBlockHanbler",
		exceptionsToIgnore = {ArithmeticException.class}
	)
	public Map degradeRuleAnno( Integer type ) {
		System.out.println( "/test/degradeRuleAnno...in..." );
		if( type == 1 ) {
			int i = 1/0;
		} else if ( type == 2 ) {
			throw new IllegalArgumentException("测试异常_捕获");
		}
		System.out.println("/test/degradeRuleAnno...正常执行..." + LocalTime.now() );
		return createResp("degradeRuleAnno.success");
	}
	public Map degradeRuleAnnoBlockHanbler( Integer type, BlockException ex ) {
//		ex.printStackTrace();
		System.out.println("/test/degradeRuleAnno...block..." + LocalTime.now() + "...【"+ ex.getMessage() +"】");
		return createResp("degradeRuleAnno.block【熔断降级】");
	}
}
